/**
* file name  : Plate.java
* authors    : Luciano Xumerle
* created    : ven 02 feb 2007 11:05:28 CET
*
* Copyright (c) 2004-2013 Luciano Xumerle. All rights reserved.
*
* This file is part of gephinfo.
*
* gephinfo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*/

package it.ciano.jenoware;

import it.ciano.util.CString;
import java.util.ArrayList;

/**
 * The Plate class manages a single wells plate.
 * Example: a plate with 12*8 wells is stored as an array of 96 Sbj objects.
 * To manage automatically the positions inside the plate each Sbj object
 * should be present 2 phenotypes: platename and plateposition (1 to 96).
 *
 * @author Luciano Xumerle
 * @version 0.0.1
 */
public class Plate {
    /**
     * Phenotype name used to set the plate name.
     */
    final static public String plateName="platename";

    /**
     * Phenotype name used to set the position into the plate.
     */
    final static public String platePosition="plateposition";

    /**
     * Phenotype name used to set the type of the Plate.
     */
    final static public String plateType="platetype";

    private String name;
    private Sbj[] sbjs;
    private int h,v,tot,withPos;

    private final static Sbj EMPTY=new Sbj ( "--" );

    private final static Sbj CTRL=new Sbj ( "CTRL" );

    private Sbj[] unpositionables;

    private String[] colNames;
    private String[] rowNames;
    private String[] coords;

    /**
     * Class Constructor.
     *
     * @param plateName the plate name.
     * @param hdim horizontal dimension of the plate.
     * @param vdim vertical dimension of the plate.
     */
    public Plate ( String plateName, int hdim, int vdim ) {
        this ( plateName, hdim, vdim, null );
    }


    /**
     * Class Constructor.
     *
     * @param plateName the plate name.
     * @param hdim horizontal dimension of the plate.
     * @param vdim vertical dimension of the plate.
     * @param subjects the list of subjects to place in the plate.
     */
    public Plate ( String plateName, int hdim, int vdim, Sbj[] subjects ) {
        unpositionables=null;
        name=plateName;
        h=hdim;
        v=vdim;
        tot=h*v;
        withPos=0;
        sbjs=new Sbj[tot];
        for ( int i=0; i<tot; i++ )
            sbjs[i]=EMPTY;

        colNames=new String[hdim];
        rowNames=new String[vdim];
        for ( int i=0; i<colNames.length; i++ )
            colNames[i]= ( i+1 ) +"";
        for ( int i=0; i<rowNames.length; i++ )
            rowNames[i]= ( i+1 ) +"";
        setNamedPositions();
        addList ( subjects );
    }


    /**
     * Set names of the Plate positions.
     */
    private void setNamedPositions() {
        coords=new String[tot];
        int count=0;
        while ( count<tot ) {
            for ( int j=0; j<v; j++ )
                for ( int i=0; i<h; i++ ) {
                    coords[count] = getColName ( i ) + "-" + getRowName ( j );
                    count++;
                }
        }
    }

    /**
     * Returns the name of the Plate.
     *
     * @return the name of the Plate.
     */
    public String getName() {
        return name;
    }


    /**
     * Returns the horizontal dimension.
     *
     * @return the horizontal dimension.
     */
    public int getHdim() {
        return h;
    }


    /**
     * Returns the vertical dimension.
     *
     * @return the vertical dimension.
     */
    public int getVdim() {
        return v;
    }


    /**
     * Returns the column name at given position (count from zero).
     *
     * @param i position.
     * @return the column name at given position.
     */
    public String getColName ( int i ) {
        return colNames[i];
    }


    /**
     * Returns the row name at given position (count from zero).
     *
     * @param i position.
     * @return the row name at given position.
     */
    public String getRowName ( int i ) {
        return rowNames[i];
    }

    /**
     * Adds a list of subjects to the plate.
     *
     * @param subjects the list of subjects to place in the plate.
     * @return true or false.
     */
    public boolean addList ( Sbj[] subjects ) {
        if ( subjects!=null && subjects.length>0 ) {
            ArrayList withoutpos=new ArrayList();

            // adds the subject with plateName equals to the name of this plate
            // and defined platePosition
            // the other subjects are stored in the withoutpos ArrayList
            for ( int i=0; i<subjects.length; i++ ) {
                String n=subjects[i].getPhenotype ( plateName ).getValue();
                int p=-1;
                try {
                    p=Integer.parseInt ( subjects[i].getPhenotype ( platePosition ).getValue() );
                } catch ( NumberFormatException e ) {
                    p=-1;
                }

                if ( withPos<tot && p>0 && n.equals ( name ) && sbjs[p-1].equals ( EMPTY ) && ! sbjs[p-1].equals ( CTRL )  ) {
                    withPos++;
                    sbjs[p-1]=subjects[i];
                } else
                    withoutpos.add ( subjects[i] );
            }

            // the withoutpos ArrayList is parset to find sbjs without plate
            // that may be used to fill the current plate.
            ArrayList remaining=new ArrayList();
            for ( int w=0; w<withoutpos.size(); w++ ) {
                boolean notInserted=true;
                Sbj t= ( Sbj ) withoutpos.get ( w );
                if ( t.getPhenotype ( plateName ).getValue().equals ( "" )  )
                    for ( int i=0; i<tot && withPos<tot && notInserted; i++ )
                        if ( sbjs[i].equals ( EMPTY ) ) {
                            /* t.addPhenotype ( plateName, name );
                            StringBuffer a=new StringBuffer();
                            a.append(i+1);
                            t.addPhenotype ( platePosition, a.toString() );  */
                            sbjs[i]=t;
                            withPos++;
                            notInserted=false;
                        }
                if ( notInserted )
                    remaining.add ( t );
            }

            // the unpositionable list is set
            if ( remaining.size() >0 )
                unpositionables= ( Sbj[] ) remaining.toArray ( new Sbj[remaining.size() ] );
        } else
            return false;
        return true;
    }


    /**
     * The position given is setted as a CTRL probe.
     *
     * @param position a number from 0 to total-1.
     */
    public void setCTRLposition ( int position ) {
        sbjs[position]=CTRL;
    }


    /**
     * The position given is setted as a CTRL probe.
     *
     * @param col Column name.
     * @param row Row name.
     */
    public void setCTRLposition ( String col, String row ) {
        int c=0;
        int r=0;
        for ( int i=0; i<colNames.length; i++ )
            if ( col.equals ( colNames[i] ) )
                c=i+1;
        for ( int i=0; i<rowNames.length; i++ )
            if ( row.equals ( rowNames[i] ) )
                r=i+1;
        setCTRLposition ( h*r - 1 - ( h-c ) );
    }


    /**
     * Sets the name of each column.
     *
     * @param names the names list.
     * @return true or false.
     */
    public boolean setColNames ( String[] names ) {
        if ( names.length == colNames.length ) {
            colNames=names;
            setNamedPositions();
            return true;
        }
        return false;
    }


    /**
     * Sets the name of each row.
     *
     * @param names the names list.
     * @return true or false.
     */
    public boolean setRowNames ( String[] names ) {
        if ( names.length ==rowNames.length ) {
            rowNames=names;
            setNamedPositions();
            return true;
        }
        return false;
    }


    /**
     * Returns strings like A-12 to get the positions from a given array positions.
     *
     * @param pos position in array.
     * @return strings like A-12 to get the positions from a given array positions.
     */
    public String getCoord ( int pos ) {
        return coords[pos];
    }

    /**
     * Returns the list of subjects not inserted in the Plate (null if the list is empty).
     *
     * @return the list of subjects not inserted in the Plate (null if the list is empty).
     */
    public Sbj[] getSbjOutOfPlate() {
        return unpositionables;
    }

    public String toHTML(  ) {
        return toHTML ( false );
    }

    /**
     * Returns the HTML code needed to show the plate.
     * The HTML plate is created using the TABLE TAG.
     *
     * @return the HTML code.
     */
    public String toHTML ( boolean noPos ) {
        int tcol=1;
        int rcol=1;

        String rows[] = new String[colNames.length+1];
        String cols[] = new String[rowNames.length];

        StringBuffer res=new  StringBuffer();

        // header
        for ( int i=0; i<colNames.length; i++ )
            rows[i]=colNames[i];
        rows[colNames.length]="+";

        res.append ( "<table border=\"1\">\n<tr><td>\n" );
        res.append ( CString.join ( rows, "</td><td>" ) + "\n</td></tr>" );
        for ( int i=0; i<sbjs.length; i++ ) {
            if ( tcol==1 )
                res.append ( "<tr>\n" );
            if ( sbjs[i].equals ( EMPTY ) )
                res.append ( "<td>--</td>"  );
            else {
                res.append ( "<td>"  );
                if ( ! noPos  ) {
                    res.append ( getCoord ( i ) );
                    res.append ( ": " );
                }
                res.append ( sbjs[i].getID() );
                if ( !  ( sbjs[i].getPhenotype ( "info" ).getValue() ).equals ( "" )          ) {
                    res.append ( "<br><small>"  );
                    res.append ( sbjs[i].getPhenotype ( "info" ).getValue() );
                    res.append ( "</small></td>\n"  );
                }
            }

            if ( tcol==h ) {
                res.append ( "<td>\n" );
                res.append ( rowNames[rcol-1] );
                res.append ( "</td></tr>\n" );
                tcol=0;
                rcol++;
            };
            tcol++;
        }
        res.append ( "</table>\n" );
        return res.toString();
    }


    /**
     * Returns the CSV plate.
     *
     * @return the CSV text.
     */
    public String toCSV() {
        int rcol=1;

        String rows[] = new String[colNames.length+1];
        String cols[] = new String[rowNames.length];

        StringBuffer res=new  StringBuffer();

        // header
        for ( int i=0; i<colNames.length; i++ )
            rows[i]=colNames[i];
        rows[colNames.length]="+";
        res.append ( CString.join ( rows, ";" )  );
        res.append ( ";plate_position" );

        // lines
        ArrayList rr=new ArrayList();
        for ( int i=0; i<sbjs.length; i++ ) {
            if ( sbjs[i].equals ( EMPTY ) )
                rr.add ( "--" );
            else
                rr.add ( sbjs[i].getID() );

            if ( rr.size() == colNames.length ) {
                for ( int l=0; l<colNames.length; l++ )
                    rows[l]= ( String ) rr.get ( l );
                rows[h]=rowNames[rcol-1];
                rr=new ArrayList();
                rcol++;
                res.append ( CString.join ( rows, ";" ) + "\n" );
            }
        }
        return res.toString();
    }


    /**
     * Returns the CSV table containing the subjects without a fixed position in the plate.
     * The CSV allows the user to update the database with the new subjects and positions
     * in the plate.
     *
     * @return the CSV table.
     */
    public String getPositionCSV() {
        boolean tobeadded=false;
        StringBuffer res=new  StringBuffer();
        res.append ( "id;plate;position;coords\n" );
        for ( int i=0; i<sbjs.length; i++ ) {
            if ( !sbjs[i].equals ( EMPTY )
                    && sbjs[i].getPhenotype ( Plate.plateName ).getValue().equals ( "" ) ) {
                tobeadded=true;
                res.append ( sbjs[i].getID()   );
                res.append ( ';'  );
                res.append ( name );
                res.append ( ';'  );
                res.append ( i+1 );
                res.append ( ';'  );
                res.append ( getCoord ( i ) );
                res.append ( ';'  );
                res.append (  sbjs[i].getPhenotype ( Plate.plateType ).getValue() );
                res.append ( "\n"  );
            }
        }
        if ( tobeadded )
            return   res.toString();
        return "";
    }

    /**
     * Override toString method.
     *
     * @return the plate string as the ordered list of ID.
     */
    public String toString() {
        StringBuffer res=new  StringBuffer();
        for ( int i=0; i<sbjs.length; i++ ) {
            res.append ( sbjs[i].getID()   );
            res.append ( "\n"  );
        }
        return   res.toString();
    }

}  // end class

